/* 
  Copyright (c) SilverLining 2015
  Date:    2016-1-11
  File:    7za.cpp  
  Description: 
	Contains the implementation of the 7za wrapper class.
*/

#include "stdafx.h"

#define CMDBUFSZ 32767

namespace m
{
	namespace compression
	{
		_7za::_7za()
			: _CommandBuffer(new WCHAR[CMDBUFSZ]),
			_hChildStd_OUT_Rd(NULL),
			_hChildStd_OUT_Wr(NULL),
			_hChildStd_ERR_Rd(NULL),
			_hChildStd_ERR_Wr(NULL)
		{
		}

		_7za::~_7za()
		{
			Reset();

			if (_CommandBuffer)
			{
				delete[] _CommandBuffer;
			}
		}

		bool _7za::package_exists(const wchar_t* packageName)
		{
			DWORD attr = GetFileAttributes(packageName);
			if (GetLastError() != ERROR_SUCCESS)
			{
				throw bad_result_exception(HRESULT_FROM_WIN32(GetLastError()), "GetFileAttributes failed.");
			}
			return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_NORMAL);
		}

		void _7za::create_package(const wchar_t* packageName, const wchar_t* packageFiles)
		{
			WCHAR ArchiverPath[MAX_PATH] = {};
			WCHAR PackagePath[MAX_PATH] = {};
			WCHAR FilePath[MAX_PATH] = {};
			WCHAR* Unused;
			GetFullPathName(packageFiles, MAX_PATH, PackagePath, &Unused);
			GetFullPathName(packageName, MAX_PATH, FilePath, &Unused);

			GetArchiverPath(ArchiverPath);

			DWORD ret = CreateChildProcess(nullptr, PackagePath, L"\"%s\" a -r -t7z -mmt \"%s\" \"*\"", ArchiverPath, FilePath);
			if (ret != 0)
			{
				DBGL(L"Actual 7za result: %d", ret);
				throw bad_result_exception(MONORET(FACILITY_7ZA, ERROR_CREATE_FAILED), "7za failed to create a package.");
			}
		}
		
		void _7za::extract_file(const wchar_t* packageName, const wchar_t * fileName, m::io::file_buffer * buffer)
		{
			WCHAR ArchiverPath[MAX_PATH] = {};
			WCHAR PackagePath[MAX_PATH] = {};
			WCHAR PackageName[MAX_PATH] = {};

			WCHAR* PackageFile;
			if (GetFullPathName(packageName, MAX_PATH, PackagePath, &PackageFile) > 0)
			{
				wcscpy_s(PackageName, MAX_PATH, PackageFile);
				*PackageFile = NULL;
			}

			GetArchiverPath(ArchiverPath);

			DWORD ret = CreateChildProcess(buffer, PackagePath, L"\"%s\" x -so -mmt \"%s\" \"%s\"", ArchiverPath, PackageName, fileName);
			if (ret != 0)
			{
				DBGL(L"Actual 7za result: %d", ret);
				throw bad_result_exception(MONORET(FACILITY_7ZA, ERROR_CREATE_FAILED), "7za failed to extract a file.");
			}
		}
		
		DWORD _7za::CreateChildProcess(io::file_buffer* outBuf, LPCWSTR WorkingDir, LPCWSTR Format, ...)
		{
			Reset();

			SECURITY_ATTRIBUTES saAttr;
			saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
			saAttr.bInheritHandle = TRUE;
			saAttr.lpSecurityDescriptor = NULL;

			if (!CreatePipe(&_hChildStd_OUT_Rd, &_hChildStd_OUT_Wr, &saAttr, 0))
			{
				throw bad_result_exception(HRESULT_FROM_WIN32(GetLastError()), "Failed to create out pipe.");
			}

			if (!SetHandleInformation(_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
			{
				throw bad_result_exception(HRESULT_FROM_WIN32(GetLastError()), "Failed to create out pipe.");
			}

			if (!CreatePipe(&_hChildStd_ERR_Rd, &_hChildStd_ERR_Wr, &saAttr, 0))
			{
				throw bad_result_exception(HRESULT_FROM_WIN32(GetLastError()), "Failed to create out pipe.");
			}

			if (!SetHandleInformation(_hChildStd_ERR_Rd, HANDLE_FLAG_INHERIT, 0))
			{
				throw bad_result_exception(HRESULT_FROM_WIN32(GetLastError()), "Failed to create out pipe.");
			}

			// Create the child process here.
			PROCESS_INFORMATION piProcInfo = {};
			STARTUPINFO siStartInfo{};
			BOOL bSuccess = FALSE;
			va_list args;

			va_start(args, Format);
			HRESULT hr = StringCchVPrintf(_CommandBuffer, CMDBUFSZ, Format, args);
			va_end(args);
			if (FAILED(hr))
			{
				throw bad_result_exception(hr, "StringCchVPrintf failed.");
			}

			siStartInfo.cb = sizeof(STARTUPINFO);
			siStartInfo.hStdError = _hChildStd_ERR_Wr;
			siStartInfo.hStdOutput = _hChildStd_OUT_Wr;
			siStartInfo.hStdInput = NULL;
			siStartInfo.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
			siStartInfo.wShowWindow = SW_HIDE;

			bSuccess = CreateProcess(NULL,
				_CommandBuffer,
				NULL,
				NULL,
				TRUE,
				0,
				NULL,
				WorkingDir,
				&siStartInfo,  // STARTUPINFO pointer 
				&piProcInfo
			);

			CloseHandle(_hChildStd_OUT_Wr);
			_hChildStd_OUT_Wr = NULL;
			CloseHandle(_hChildStd_ERR_Wr);
			_hChildStd_ERR_Wr = NULL;

			if (!bSuccess)
			{
				throw bad_result_exception(HRESULT_FROM_WIN32(GetLastError()), "CreateProcess failed.");
			}

			ReadFromPipe(_hChildStd_OUT_Rd, outBuf);
			ReadFromPipe(_hChildStd_ERR_Rd, nullptr);

			DWORD exitCode;
			if (!GetExitCodeProcess(piProcInfo.hProcess, &exitCode))
			{
				exitCode = (DWORD)-1;
			}
			CloseHandle(piProcInfo.hThread);
			CloseHandle(piProcInfo.hProcess);
			return exitCode;
		}

		void _7za::ReadFromPipe(HANDLE hOutPipe, io::file_buffer* buf)
		{
			DWORD dwRead;
			CHAR chBuf[4096];
			BOOL bSuccess = FALSE;

			if (buf)
			{
				buf->clear();
			}

			for (;;)
			{
				bSuccess = ReadFile(hOutPipe, chBuf, ARRAYSIZE(chBuf), &dwRead, NULL);
				if (!bSuccess || dwRead == 0) break;
				if (buf)
				{
					buf->write(chBuf, (size_t)dwRead);
				}
			}
		}

		void _7za::GetArchiverPath(WCHAR ArchiverPath[MAX_PATH])
		{
			GetCurrentDirectory(MAX_PATH, ArchiverPath);
			PathCchAppend(ArchiverPath, MAX_PATH, L"7za.exe");
		}

		void _7za::Reset(void)
		{
			if (_hChildStd_OUT_Rd)
			{
				CloseHandle(_hChildStd_OUT_Rd);
				_hChildStd_OUT_Rd = NULL;
			}
			
			if (_hChildStd_OUT_Wr)
			{
				CloseHandle(_hChildStd_OUT_Wr);
				_hChildStd_OUT_Wr = NULL;
			}

			if (_hChildStd_ERR_Rd)
			{
				CloseHandle(_hChildStd_ERR_Rd);
				_hChildStd_ERR_Rd = NULL;
			}

			if (_hChildStd_ERR_Wr)
			{
				CloseHandle(_hChildStd_ERR_Wr);
				_hChildStd_ERR_Wr = NULL;
			}
		}
	}
}